package yconv

import "reflect"

type Struct struct {
	Name            string
	Description     string
	Package         string
	Domain          string
	RouterRoot      string
	DomainCamLowCap string
	Fields          []StructField
	CamelStyle      bool
}
type StructField struct {
	FieldName   string
	JsonName    string
	Type        string
	JSType      string
	Tag         reflect.StructTag
	Value       reflect.Value
	Description string
}

func GetObjectFields(obj interface{}, ignoreStructs ...any) (*Struct, error) {
	opt := &option{ignoreStructs: ignoreStructs}
	opt.init()
	return visitObjectFields(obj, nil, opt)
}

type option struct {
	ignoreStructs []any
	_structs      map[string]bool
}

func (o *option) init() {
	if o._structs == nil {
		o._structs = make(map[string]bool)
	}

	for _, s := range o.ignoreStructs {
		t := reflect.TypeOf(s)
		o._structs[t.String()] = true
		if t.Kind() == reflect.Ptr {
			o._structs[t.Elem().String()] = true
		}
	}
}

func (o *option) shouldIgnore(f reflect.StructField) bool {
	if o._structs == nil {
		return false
	}
	return o._structs[f.Type.String()]
}

func visitObjectFields(obj interface{}, cover *Struct, opt *option) (*Struct, error) {
	field := reflect.TypeOf(obj)
	value := reflect.ValueOf(obj)

	if cover == nil {
		cover = &Struct{
			Name:    field.Name(),
			Package: field.PkgPath(),
		}
	}

	switch field.Kind() {
	case reflect.Ptr:
		field = field.Elem()
		value = value.Elem()
	case reflect.Struct:
		break
	default:
		return cover, nil
	}

	for i := 0; i < field.NumField(); i++ {
		f := field.Field(i)
		v := value.Field(i)
		if opt.shouldIgnore(f) {
			continue
		}

		if f.Anonymous {
			if _, err := visitObjectFields(v.Interface(), cover, opt); err != nil {
				return nil, err
			}
			continue
		}

		cover.Fields = append(cover.Fields, StructField{
			FieldName:   f.Name,
			JsonName:    f.Tag.Get("json"),
			Type:        f.Type.String(),
			Tag:         f.Tag,
			Value:       v,
			JSType:      TypeToJsType(f.Type),
			Description: f.Tag.Get("comment"),
		})

	}
	return cover, nil
}

func TypeToJsType(t reflect.Type) string {
	switch t.Kind() {
	case reflect.String:
		return "string"
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return "number"
	case reflect.Bool:
		return "boolean"
	default:
		return "any"
	}
}
